#include "Kernel.h"
#include "Utils/Spinlock.h"
#include "Utils/List.h"
#include "Utils/TList.h"
#include "Scheduler/Scheduler.h"
#include "Scheduler/Session.h"
#include "Scheduler/Team.h"
#include "Scheduler/Process.h"
#include "Scheduler/Thread.h"
#include "Hal/IHardware.h"
#include "Drivers/Console.h"

extern "C"
{
	extern uint32 Kernel_Stack;
	extern uint32 Kernel_StackSize;
}


Session * Scheduler::SystemSession;
Thread * Scheduler::SystemThread;
Session * Scheduler::CurrentSession;
Team * Scheduler::CurrentTeam;
Process * Scheduler::CurrentProcess;
Thread * Scheduler::CurrentThread;
Spinlock * Scheduler::SchedulerLock;
List * Scheduler::RunningThreads;


Result Scheduler::Initialize( void )
{
	SCHEDULER_TRACE( "Initializing...\n" );

	SchedulerLock = new Spinlock();
	RunningThreads = new List();

	SystemSession = new Session( "System" );
	SystemThread = CurrentThread = GetSystemThread();

	sHardware->AddInterruptHandler( 0xFF, &Scheduler::Schedule );
	return RESULT_OK;
}


Thread * Scheduler::GetSystemThread( void )
{
	Thread * thread = new Thread( "Kernel", &Kernel_Stack, Kernel_StackSize );
	thread->state = THREAD_RUNNING;

	return thread;
}


void Scheduler::StartThread( Thread * thread )
{
	ASSERT( thread != NULL );
	PARANOID_ASSERT( thread->signature == THREAD_SIGNATURE );

	RunningThreads->Add( thread );
}


int Scheduler::GetThreadCount( void )
{
	return RunningThreads->GetCount();
}


void Scheduler::DumpThread( Thread * thread )
{
    if( thread == NULL )
    {
        SCHEDULER_TRACE( "\tThread (null):\n" );
        return;
    }

	PARANOID_ASSERT( thread->signature == THREAD_SIGNATURE );

	SCHEDULER_TRACE( "Thread %p \"%s\":\n", thread, thread->GetName() );
	SCHEDULER_TRACE( "\tStack: %08x\n", thread->stack );
	SCHEDULER_TRACE( "\tSize:  %08x\n", thread->stackSize );

#if TRACE_SCHEDULER_DETAILED
	TRACE_CONTEXT( thread->archContext );
#endif
}


void Scheduler::Schedule( ITaskContext * context )
{
	ASSERT( context != NULL );

	if( RunningThreads->GetCount() == 0 )
	{
	    SCHEDULER_TRACE_DETAILED( "No threads to switch too, ignoring schedule call.\n" );
		return;
	}

	SchedulerLock->Lock();

	Thread * prev = CurrentThread;
	Thread * next = (Thread *)RunningThreads->GetFirst();

	if( next == prev || next == NULL )
	{
	    SCHEDULER_TRACE_DETAILED( "Prev or next thread is NULL, prev = %p, next = %p.\n", prev, next );
		SchedulerLock->Unlock();
		return;
	}

	PARANOID_ASSERT( prev->signature == THREAD_SIGNATURE );
	PARANOID_ASSERT( next->signature == THREAD_SIGNATURE );

	CurrentThread = next;

	// only return it to the running threads list if it was actually running...
	if( prev->state == THREAD_RUNNING )
	{
		prev->state = THREAD_READY;
		RunningThreads->Add( prev );
	}

	RunningThreads->Remove( next );
	next->state = THREAD_RUNNING;

	SchedulerLock->Unlock();

#if 0
	SCHEDULER_TRACE_DETAILED( "Old Thread %p: %s...\n", prev, prev->GetName() );
	DumpThread( prev );

	SCHEDULER_TRACE_DETAILED( "New Thread %p: %s...\n", next, next->GetName() );
	DumpThread( next );

	SCHEDULER_TRACE_DETAILED( "Thread Listing: %i total.\n", RunningThreads->GetCount() );
	for( ListNode * node = RunningThreads->GetFirst(); node != NULL; node = node->GetNext() )
	{
		Thread * thread = (Thread *)node;
		SCHEDULER_TRACE( "Thread %p: %s\n", thread, thread->GetName() );
	}
#else
	SCHEDULER_TRACE_DETAILED( "Old Thread %p: %s...\n", prev, prev->GetName() );
	SCHEDULER_TRACE_DETAILED( "New Thread %p: %s...\n", next, next->GetName() );
#endif

	if( prev != NULL )
		sHardware->SwitchContext( prev->context, context, next->context );
	else
		sHardware->SwitchContext( NULL, context, next->context );

	return;
}


void Scheduler::Yield( void )
{
	ASSERT( CurrentThread != NULL );
	PARANOID_ASSERT( CurrentThread->signature == THREAD_SIGNATURE );

	// no other threads, do don't bother yielding
	if( RunningThreads->GetCount() == 0 )
	{
	    SCHEDULER_TRACE( "No threads to switch too, ignoring yield call.\n" );
		return;
	}

	SCHEDULER_TRACE( "Thread %p \"%s\" yielding...\n", CurrentThread, CurrentThread->GetName() );

	sHardware->YieldContext( CurrentThread->context );
}


